<? 


/***
  	Sitecomfort Debugger 
	Обработчик ошибок, отладчик и профайлер на PHP

	Возможности:
	0) Использование: просто подключите дебагер перед телом скрипта. Все ошибки, возникшие после подключения дебагера будут зафиксированы. 
	1) Отлавливает все виды ошибок PHP, в том числе ошибки парсинга (в дочернимх подключаемых файлах, по отношению к тому файлу, в котором подкюлючается дебаггер). Скрывает все ошибки от обычного посетителя.
	2) О фатальных ошибках сообщает разработчику на e-mail, а пользователю показывает нейтральное собщение об ошибке.
	3) Извлекает всю полезную информацию о месте возникновении ошибки (описание, файл, номер строки, значения важных переменных, url-запрос и другие )
	4) Фиксирует только уникальные, не повторяющиеся ошибки 
	5) Записывает информацию в лог-файл ошибок. 
	6) Поддерживает авторизацию для разработчика. Авторизованому разработчику показывает панельку внизу страницы с обзорной информации об ошибках и затраченных ресурсах. При клике на соответствующий пункт - отображает один из подробных отчетов: об ошибках, расходе времени, расходе памяти и другие. 
	7) В отчет по ошибках , по каждой ошибке отображает строка PHP кода, где возникла ошибка, и несколько строк перед ней
	8) Позволяет исследовать каждую ошибку, проследив обратно историю вызов функций и передаваемых в них параметров.
	9) Корректно обрабатывает и позволяет исследовать ошибки в eval() коде
	

	
	Требования:
	PHP 5+	
	Желательно PHP 5.2+ (для функции error_get_last)
	
	
	Аналоги, для заимствования приемов и функций:
	http://www.gosu.pl/debug/
	
	
***/

// return; 

require_once(cfg()->CORE_DIR."/functions.php");

Debugger::init();

class Debugger {	
	
	// массив с отловленными ошибками 
	static $errors = array();
	
	// буфер для резервирования памяти, чтобы её хватило на обработку фатальных ошибок памяти
	static $temp_buffer; 
		
	// настройки отображения дебаггера  
	static $opt =  array();	
	
	// флаг авторизованого разработчика  
	static $is_developer=null;
	
	// сессия авторизованого разработчика  
	static $session=array();
	
	 
	
	
	// время старта скрипта  
	static  $start_time=0;
	
	// максимуум использования памяти
	static  $memory_usage_max=0;
	
	// место максимального расхода памяти
	static  $memory_usage_max_point='';
	
	static  $error_log_maxsize=1000000; // макс. размер лог файла ошибок
	
	// уровень логгируемых ошибок
	static $error_reporting=-1;
	
	// инициализация дебаггера
	static function init()
	{
		// фиксируем момент старта  
		self::$start_time = microtime(true);
		
		//  включаем сохранение сообщения об ошиибе в глобальной переменной $php_errormsg (используется, если PHP<5.2 )
		ini_set('track_errors', 1);
		
		// включаем вывод ошибок на веб-странице (при этом нефатальные ошибки (warning, notice и другие) будут перехватываться обработчиком error_handler, а фатальные ошибики будут выводиться и потом вырезаться в обработчике output_handler установленом в ob_start() )
		ini_set("display_errors", 1);
	
		// ловим все виды ошибок
		self::$error_reporting = -1;
		error_reporting(self::$error_reporting);
		
		//error_reporting(E_ALL);
		// отображаем только фатальные ошибки (и потом вырезаем)
		//error_reporting(E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR );

		// настраиваем оформление кода для сообщений об ошибках, чтобы легко было распознавать их в коде веб-страницы  
		ini_set('error_prepend_string','[[[<Span class=PHP_errormsg >');
		ini_set('error_append_string', '</Span>]]]');
		ini_set('html_errors', 0);
		
		
		// отключаем HTML-оформление ошибок, создаваемое модулем xDebug
		if (function_exists('xdebug_disable')) xdebug_disable();
		
		// ловим ошибки фатальные и ошибки парсинга (для любых посетителей)
		// обработчик фатальных ошибок + вывод полного отчета дебаггера
		ob_start(array(__CLASS__, 'output_handler'));
		
		// обработчик завершения - выводит панельку дебагера внизу страницы
		// TODO:: перенести в output_handler
		register_shutdown_function(array(__CLASS__, 'shutdown_handler')); 
		
		// резервируем память для обработки фатальных ошибок нехватки памяти 
		self::$temp_buffer = str_repeat('x', 1024 * 200); // 200 Кб
		
		
		/* определяем конфигурацию отображения отчетов */
		
		// распознаем разработчика
		$is_developer= self::is_developer();
		
		// интерпретируем параметры URL
		self::$opt = array(
			
			// отображать ли отчеты дебаггера?
			'debugger'	=> isset($_GET['debugger']),
			
			// какой отчет отображать 
			'page'		=> isset($_GET['debugger']) 
								? GET('dbg_page', 'warnings') 
								: '',
			
			// фильтр по имени файла
			'filter_file'	=> GET('dbg_filter_file'),
			
		); 
		
		// отображать ли основное содержимое страницы под отчетом?
		// определяем и запоминаем в куках
		self::$opt['show_content'] = val($_GET['dbg_showcontent'],
										 val($_COOKIE['dbg_showcontent']));
										 
		setcookie('dbg_showcontent', self::$opt['show_content'], 0, '/');
		
		
		// ловим обычные ошибки 
		set_error_handler(array(__CLASS__, 'error_handler'));
		
		// pr(ini_get('error_log'));
		
		// если не определен файл для лога ошибок - определяем его
		if (!($log=ini_get('error_log')) 
			|| !is_writable(dirname($log)))
		{
			ini_set('error_log', $log=cfg()->SYSTEM_ROOT.'/system_data/logs/errors.log');
			
			if (!file_exists($d=dirname($log))) mkdir($d);
			
			trigger_error("В настройках PHP не определен корректный путь к логу ошибок. Чтобы отслеживать ошибки любых PHP скриптов, добавьте в .htaccess следующую директиву: <br/><br/> php_value error_log \"$log\" <br/> ", E_USER_WARNING );
		}
		
		
		if (file_exists($log=ini_get('error_log')))
		{
			if (!is_writable($log))
				trigger_error("Не установлены права на запись в лог-файл ошибок \"$log\" ", E_USER_WARNING );
			
			// очищаем лог-файл по запросу
			if (self::$opt['debugger'] && GET('clear_log'))
			{
				file_put_contents($log, '');
				$url = str_replace('clear_log=1','', $_SERVER['REQUEST_URI']);
				redirect($url);
			}	
			
			// если лог-файл превышает допустимый размер - обрезаем его, оставляя только несколько последних записей (10Кб)
			if (filesize($log) > self::$error_log_maxsize)
			{
				$logcontent = file_get_contents($log);
				$logcontent = substr($logcontent, strlen($logcontent)-10000);
				file_put_contents($log, $logcontent);
			}	
		
		}
		
		
		// остальное ниже - только для авторизованного разработчика
		if (!self::$is_developer) return;
		
		// если заказано - следим за расходом времени и памяти КАЖДЫЙ шаг
		if (self::$opt['page']=='memory' 
			OR self::$opt['page']=='time' 
			)
		{	
			//// !!! declare ticks - не работает под Window! (вылетает Апач) и как пишут - изредка не работает на Unix
			// register_tick_function(array(__CLASS__, 'tick_handler'));
			// declare(ticks=1);
		}
		
		// TODO: здесь нужно будет очистить переменные запроса (GET, REQUEST, SERVER[REQUEST_URI] и другие) от всех URL-параметров дебага, чтобы они не влияли не работу других модулей
		// unset($_GET['debugger'],...)
		// dump(self::$opt);

	}
	
	
	// обработчик для фатальных ошибок + генератор отчетов
	static 
	function output_handler($output)
	{
		//return get_dump(error_get_last());
		//return false;
		
		// освобождаем память для обработчика фатальных ошибок
		self::$temp_buffer='';
				
		// получаем информацию о последней фатальной ошибке, прервавшая выполнение скрипта
		$err = self::error_get_last($output);
		
		// return $output.'<hr>'.get_dump($err);
		
		
		if ( $err AND self::is_fatal( $err['type'] ))
		{
			self::catch_error($err, $output);
			 
			// если посетитель - авторизованый разработчик,  
			// то сразу переходим в режим отчета по ошибке
			if (self::$is_developer)
			{
				self::$opt['debugger']	= true;
				if (empty(self::$opt['page']))
						self::$opt['page'] = 'warnings';
			}
			// если посетитель - не разработчик, то нейтрально сообщаем ему об ошибке и шлеем письмо разработчику
			else //if (!self::$is_developer)	
			{	
				$output = 
				'
					Невозможно отобразить веб-страницу, так как произошла серьезная ошибка. Попробуйте зайти позже.<br/>
					<a href="javascript:history.back()" >назад</a>&nbsp;&nbsp;
					<a href=/ >на главную</a>&nbsp;&nbsp;
				'
				  // на локальном хосте показываем ссылка на дебаггер	
				.( strstr( str_replace('www.','', $_SERVER['HTTP_HOST']),'.') ? '' 
					:  '<a href=?debugger >дебаггер</a>&nbsp;&nbsp;' 
				)
					
				
				.str_repeat(' ', 1200 ); // обеспечиваем минимальное число символов документа для отображения ошибок в IE
			
				//TODO: здесь нужно сообщить разработчику про фатальную ошибку на сайте, но не слишком часто (1-2 раза в месяц про одну и ту же ошибку)
				
				//mail(self::$developer_email, 'Ошибка на сайте $_SERVER['HTTP_HOST']', 'Произошла фатальная ошибка PHP при запросе страницы http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'] );
			
			
			}
		}// end if ($err)
		
		
		
		
		// если полный отчет не запрошен, не меняем контент
		if (empty(self::$opt['debugger'])) return $output;
		
		// если ошибок не было зафиксировано, не меняем контент
		//if (!count(self::$errors)) return $output;
		
		
		
		// получаем HTML-отчет о всех ошибках (или форму авторизации для разработчика)
		$page = self::view_page();
		
		
		// в зависимости от URL, - выводим только отчет...
		if (!self::$opt['show_content']) $output=$page;
		// или присоединяем отчёт сверху страницы
		else
		{	// присоединяем отчет в начало документа после тега BODY
			if (!stristr($output, '<body'))
				$output = $page . $output;
			else	
				$output = preg_replace( 
							'/(<body[^>]*>)/i' ,  
							'$1'.$page, 
							$output
							);
		}
		
		
		
		return $output; 
	}
	
	// Debugger::error_get_last($output='') 
	// эмулирует функцию error_get_last (введенную в php5.2)
	// $output - содержимое веб-страницы (передается, если метод вызывается из обработчика, заданного в ob_start() )
	static
	function error_get_last(&$output='') 
	{
		// если возможно, используем error_get_last() (PHP 5.2+)
		if (function_exists("error_get_last")
			 AND 
				( !phpversion('xdebug') 
					OR
					version_compare(phpversion('xdebug'), '2.0.0', '>=')
				)	// если подключен xDebug, то используем error_get_last() только в том случае, если  версия xDebug >= 2.0.0 (до этой версии он обнулял error_get_last() )
			)
			return error_get_last();
		
		
		// если нет возможности использовать error_get_last(), 
		// то извлекаем ошибку из текста веб-старницы
		
		$prefix = ini_get('error_prepend_string');
		$suffix = ini_get('error_append_string');
		$prefix_start = strpos($output, $prefix);
		$suffix_start = strpos($output, $suffix);
				
		// определяем, есть ли в коде веб-страницы наше оформление для ошибок
		if ($output && $suffix_start)
		{
			
			
			// извлкаем сообщеие об ошибке 
			$error_start =  $prefix_start + strlen($prefix);
			
			$error_str = trim(strip_tags(substr($output, 
							$error_start, 
							$suffix_start - $error_start 
							)));
			
			
			// извлекаем свойства ошибки
			// (протестировано на многих сообщениях об ошибках)
			preg_match('~([^:]+): (.+) in (.+) on line (\d+)~s' , 
				$error_str , $match);
				
			if(empty($match)) return NULL;
			
			// вырезаем сообщения об ошибках из кода страницы
			while 
			( 	//0&&
				FALSE !== ($start = strpos($output, $prefix))
				&&
				($end = strpos($output, $suffix)+strlen($suffix) )
				&&
				$end > $start
			)
			{
				$output = substr_replace($output ,'', $start, $end-$start);
			}
			
			
			// формируем массив со свойствами ошибки
			$error = array(
				'error_str'	=> $match[0],
				'type_str'	=> $match[1],
				'message'	=> $match[2],
				'file'		=> $match[3],
				'line'		=> $match[4],
				);
			
			// упрощенно вычислем номер ошибки
			if ($error['type_str']=='Parse error') $error['type']=4;
			else if (strstr($error['type_str'],'error')) $error['type']=1;
			else $error['type']=0;
			
			
			// обрабатываем ошибки в eval()
			if (strstr($error['file'], 'eval()'))
			{
				$error['message'] = "eval(): ".$error['message'];
				
				$error['eval_line']	= $error['line'];
				$error['line'] 	= intval(
								  substr($error['file'], 
										strpos($error['file'],'(')+1
										));
				$error['file'] 	= substr($error['file'],
										0, strpos($error['file'], '(' )
										);
			}
			
			return $error;
			
		}
		else
		if (val($GLOBALS['php_errormsg'])) 
		{
			return array(
				"type" => 0,
				"message" => $GLOBALS['php_errormsg'],
				"file" => null,
				"line" => null,
			);
		}
		else return null;
		
	}
	
	
	
	// счетчики ошибок
	static $counter = array('errors_all'=>0, 'errors_warnings'=>0);
		
	// Расшифровки для типа ошибки
	// http://php.net/manual/en/errorfunc.constants.php
	static $error_types = array(
		0	=> "UNKNOWN_ERROR (%num%)",
		1	=> "ERROR",
        2	=> "WARNING",
        4	=> "PARSE_ERROR",           
        8	=> "NOTICE",                
        16	=> "CORE_ERROR",            
        32	=> "CORE_WARNING",          
        64	=> "COMPILE_ERROR",         
        128 => "COMPILE_WARNING",       
        256		=> "USER_ERROR",            
        512		=> "USER_WARNING",          
        1024	=> "USER_NOTICE",           
        2048	=> "STRICT_STANDARTS_NOTICE",          
        4096 	=> "RECOVERABLE_ERROR", 
		8192	=> "DEPRECATED_NOTICE",
        16384	=> "USER_DEPRECATED_NOTICE",
    );
	
	static
	function count_errors($error_num)
	{
		self::$counter['errors_all']++;
		if (in_array($error_num, array(1,2,4,16,32,64,128,256,512,4096)))
			self::$counter['errors_warnings']++;
	}
	static function is_fatal($error_num)
	{
		return in_array($error_num, array(1,2,4,16,256));
	}
	
	// обработчик обычных (не фатальных) ошибок
	static 
	function error_handler($errType, $errMsg, $errFile, $errLine, $errContext)
	{
		
		if (!self::$is_developer) return;
		// TODO:: после внедрения функции записи в лог и отложенного просмотра лога ошибок, здесь нужно обрабатывать ошибки уровня warning и заносить в лог не тлько для разработчика, но и для всех посетителей
		
		self::catch_error(array(
			'type'		=> $errType,
			'message'	=> $errMsg,
			'file'		=> $errFile,
			'line'		=> $errLine,
			'backtrace'	=> self::$opt['page']=='error_dump' ?  debug_backtrace() : null, 
			'context'	=> self::$opt['page']=='error_dump' ?  $errContext : null,
			 
		));
	
	
		//return false; // возвращаем false, чтобы стандартно отображались фатальные ошибки (только так работает почему-то)
		
	}
	
	
	// дописывает краткий отчет дебаггера внизу страницы
	static function shutdown_handler()
	{
		
		// pr(self::parse_error_log());
		
		if(ob_get_level() > 0) ob_end_flush(); // отправляем контент в браузер (чтобы правильно расчитать время генерации страницы, если вывод займет время)
		
		// сохраняем сессию разработчика
		self::save_dbg_session();
		
		// выводим краткий отчет дебагера в конце веб-страницы
		if (empty(self::$opt['debugger']))
			echo self::view_menu(); 
			
		
		
			
	}
	
	
	
	// регистрируем состояние памяти и времени скрипта
	static function tick_handler()
	{
	
	}
	
	
	// возвращает время от начала работы скрипта, в милисекндах
	static function script_time()
	{
		return round(1000*(microtime(true) - self::$start_time));
	}
	
	// возвращает максимальную величину выделенной памяти, в килобайтах
	static function  memory_usage_max()
	{
		return round(self::$memory_usage_max/1000);
	}
	
	// возвращает базовый URL страницы, без переменных дебаггера
	// если $full_url=true, то возвращается полный URL исходной страницы, и в конце не будет добавляться ? или &
	static function base_url($full_url=false)
	{
		list($base_url) = explode('debugger', 
			$_SERVER['REQUEST_URI'].'debugger');
		$base_url = rtrim($base_url,'?&');
		
		if ($full_url)
			$base_url = 'http://'.getenv('HTTP_HOST').$base_url;
		else 
			$base_url .= strstr($base_url, '?') ? '&' : '?';
		
		return $base_url;
	}
	
	static function current_url($append_amp=false)
	{
		$page_url = 'http://'.getenv('HTTP_HOST').$_SERVER['REQUEST_URI'];
		
		$page_url .= strstr($page_url, '?') ? '&' : '?';
		
		return $page_url;
	}
	
	
	// возвращает двумерный массив даных для строк отчета 
	static
	function get_page_data()
	{
		static 	$data=array();
		if($data) return $data;
		
		$page_name = self::$opt['page'];
		
		switch ($page_name) 
		{ 
			default:
			case ('warnings'): 
				foreach (self::$errors as $error)
					if ( stristr($error['type'], 'warning')
						 OR stristr($error['type'], 'error')
						) 
						$data[] = $error;
				break; 
				
			case ('errors_all'):
			
				return self::$errors;
				break; 
			
			case ('errors_log'):
			
				return self::parse_error_log();
				break; 
			
			
			case ('error_dump'):
				
				return 
				isset(self::$errors[$key=GET('error_key')]) 
					? array(self::$errors[$key])
					: null;
				
				break;
			
			
			case ('time'):
			
				break; 	
				
			case ('memory'):
			
				break; 	
			
			
		} //end switch ($page_name) 
		
		return $data;
		
	}
	
	// записывает все накопленные ошибки в лог
	static 
	function save_log() 
	{
		//http://phpclub.ru/detail/article/2002-10-03
	}
	
	// стартует или продолжает сессию разработчика
	function start_dbg_session($id='')
	{
		$path = ini_get('session.save_path') . '/sessdbg_';
		
		if ($id 
			&& file_exists($file = $path.$id)
			&& $data = file_get_contents($file) 
			)
			self::$session = unserialize($data);
		else
		{
			$id = uniqid(); $file = $path.$id;
			self::$session = array(
				'session_id'		=> $id,
				'session_filepath'	=> $file,
				);
			file_put_contents($f, serialize(self::$session));
		}
	}
	
	// стартует или продолжает сессию разработчика
	static function save_dbg_session()
	{
		if (self::$is_developer 
			&& self::$session
			&& ($filepath = self::$session['session_filepath'])
			)
			file_put_contents($filepath, serialize(self::$session));
	}
	
			
	// авторизует админа-разработчика
	static 
	function is_developer() 
	{
		//static $is_developer = null;
		if (self::$is_developer!==null) return self::$is_developer;
		
		self::$is_developer = false;
		
		if ( ($pass=POST('developer_pass')) 
			  && $pass == '76' 
			)
		{
			// создаем новую сессию разработчика
			//self::start_dbg_session();
			$dev_id = 1;//self::$session['id']
			setcookie('is_developer', $dev_id, 0, '/');
			self::$is_developer = true;
			
			header('Location: '
				.'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']
				, true);
			exit;
			
		}	
		else 
		if (isset($_GET['developer_logout']))
		{
			$_COOKIE['is_developer']=-1;
			setcookie('is_developer',-1, 0, '/');
			self::$is_developer = false;
			// удаляем сессию разработчика
			// ...
			
			
			$url = 'http://'.$_SERVER['HTTP_HOST']. rtrim(str_replace('developer_logout','',$_SERVER['REQUEST_URI']), '?&');
			
			header('Location: '.$url, true);
			exit;
			
		}
		else
		if (val($_COOKIE['is_developer']) > 0)
		{
			// восстанавливаем сессию разработчика
			// ...
			
			self::$is_developer = true;
		}
		
		//var_dump($_COOKIE);exit;
		
		return self::$is_developer;	
	}

	

	// преобразует лог-файл ошибок в массив объектов
	static function  parse_error_log()
	{
		$result=array();
		$error_log = file(ini_get('error_log'));
		//return $error_log;
		
		$max=count($error_log)-1;
		for($i=$max; $i>=0; $i--)
		{
			$error_str=$error_log[$i];
			
			if (!trim($error_str)) continue;
			
			//if(! preg_match('~\[([^]]+)\] ([^:]+):\s*(.+) in (.+) on line (\d+)~s' , $error_str , $match)) continue;
			// иногда \d+ почему-то не срабатывало :( 
			
			if(! preg_match('~\[([^]]+)\] ([^:]+):\s*(.+) in (.+) on line\s+(.+)~s' , $error_str , $match)) continue;
			
			// if(!trim($match[3]) || !trim($match[4])) continue;
			
			$error = array(
				'date'	=> $match[1],
				'file'	=> $match[4],
				'line'	=> $match[5],
				'type'	=> $match[2],
				'message'=>  $match[3],
				'error_str'	=> $error_str,
			);
			$e = self::catch_error($error, false);
			
			if($e['key'] && !isset($result[$e['key']]))
				$result[$e['key']] = $e;
			
			//$result[]=$e;	
		}
		
				
		return $result;
		
	}
	
	// сохраняет информацию об ошибке в массив ошибок
	static 
	function catch_error($error, $save_error=true) 
	{
		if (!$error) return;
		
		// создаем хеш для ошибки, чтобы предотвратить сохранение повторяющихся ошибок 
		
		$message4key = preg_replace('~\(url=[^)]+\)~','',$error['message']);
		//$error['message'] = $message4key;
		$error_key = md5( $error['file'].$error['line'].$message4key );
		$error_key = substr($error_key,16);
		
		// если такая ошибка уже зафиксирована - пропускаем её
		if (isset(self::$errors[$error_key])) return;
		
		
		
		//if(empty(self::$opt['debugger'])) return; // это не вличет на общее время исполнения
		
		$error['key'] = $error_key;
		// подготавливаем подробную инфомацию об ошибке
		
		// создаем строковое обозначение для типа ошибки
		$error['num'] = $error['type'];
		$error['type'] = isset(self::$error_types[$error['num']])
							? self::$error_types[$error['num']]
							: ( is_numeric($error['num']) 
								? str_replace('%num%', $error['type'] ,
											self::$error_types[0] ) 
								: $error['type']
							   );
		
		// если номер ошибки не указан, определяем его по строке типа ошибки 
		if (!is_numeric($error['num']))
		{
			
			if (stristr($error['type'],'notice'))	$error['num']=8;
			else 
			if (stristr($error['type'],'parse')) 	$error['num']=4;
			else 
			if (stristr($error['type'],'warning')) 	$error['num']=2;
			else 
			if (stristr($error['type'],'error')) 	$error['num']=1;
			else 
			$error['num']=0;
		}
		
		
		// обрабатываем сообщение об ошибке внутри  eval() code 
		if (strstr($error['file'], '('))
		{
			$error['line_eval'] = $error['line'];
			list ($error['file'], $error['line'])=
				explode('(', $error['file'] );
			
			$error['line'] = intval($error['line']);
			$error['message'] .= " (eval() code error in line $error[line_eval])"; 
		}
			
		
		// вычисляем краткое имя файла (от корня сайта) для отчета
		$error['file_url'] = $error['file'];
		$error['file_url'] = str_replace('\\','/', $error['file_url']);
		$error['file_url'] = str_replace($_SERVER['DOCUMENT_ROOT'],'', $error['file_url']);
		// удаляем из префикса - путь к системному корню и букву диска
		$error['file_url'] = str_replace(cfg()->SYSTEM_ROOT,'', $error['file_url']);	
			if($error['file_url'][1]==':') 
				$error['file_url'] = substr($error['file_url'],2);
		
		$error['file_short'] = ''
			.'..' .basename(dirname($error['file']))
			.'/'  .basename($error['file']);
		
		
		// сохраняем информацию об ошибке  
		if ($save_error)
		{
			// добавляем ошибку в массив отловленных ошибок
			self::$errors[$error_key] = $error;
			
			// записываем ошибку в лог-файл (логгируем только те ошибки, которые соответствуют установленному уровню ошибок error_reporting )
			$log_level = self::$error_reporting;
			// pr($log_level);
			//pr(ini_get('error_log'));
			
			if ($log_level<0 || ($log_level & $error['num']) )
				error_log(sprintf(	"PHP %s:  %s in %s on line %d", 
					$error['type'], 
					$error['message'] ." (url=$_SERVER[REQUEST_URI])"
					//."(error_reporting=$log_level)"
					,
					$error['file'], 
					$error['line']
					));

			
			
			// изменяем счетчики ошибок
			self::count_errors($error['num']);
			
			// фиксируем использование памяти
			if ( function_exists('memory_get_usage') 
				&& 
				( ($mem = memory_get_usage()) > self::$memory_usage_max )
				)
			{
				self::$memory_usage_max = $mem;
				self::$memory_usage_max_point = $error['file'].':'.$error['line'];
			}
		}
		
		return $error;
	} 
	
	// добавляет HTML расцветку для кода
	static
	function highlight_code($code)
	{
		$code = htmlspecialchars($code, ENT_QUOTES);
		/*
		'&' (ampersand) becomes '&amp;' 
		'"' (double quote) becomes '&quot;' when ENT_NOQUOTES is not set. 
		''' (single quote) becomes '&#039;' only when ENT_QUOTES is set. 
		'<' (less than) becomes '&lt;' 
		'>' (greater than) becomes '&gt;' 
		*/
		// таблица цветов - http://35rus.ru/htmlcolor.php
		
		
		$replaces = array(
			// символ ; НЕ в составе HTML мнемоники
			'/([^&]{5})(;)/'		=> '$1<b style="color:#008">$2</b>',
			
			// строки
			"~&quot;.*&quot;~Us"	=> '<span style="color:#090" >$0</span>',
			"~&#039;.*&#039;~Us"	=> '<span style="color:#090" >$0</span>',
			
			// круглые скобки
			'/([\(\)]+)/'		=> '<b style="color:red">$1</b>',
			// фигурные скобки
			'/([{}]+)/'			=> '<b style="color:HotPink">$1</b>',
			// квадратные скобки
			'/([\[\]]+)/'			=> '<b style="color:#B00">$1</b>',
			
			// переменные
			'~\$[a-zA-Z0-9_\[\]]+~'	=> '<span style="color:#008">$0</span>',
			
			// однострочные комментарии
			'~(//[^\r\n]*)~'	=> '<span style="color:#777">$1</span>',
			// многострочные комментарии
			'~(/\*.*\*/)~Us'		=> '<span style="color:#777">$1</span>',
			
			
		);
		
		return preg_replace(array_keys($replaces), $replaces, $code);
	}

	// возвращает HTML-табличку с пронумерованным кодом из файла
	static
	function get_code($file, $line_start, $line_end )
	{
		// проверяем файл на существование
		if (!file_exists($file)) { trigger_error("file \"$file\" not exists"); return '';}
		
		// скачиваем строки файла в память
		//
		static $files=array();
		if (!isset($files[$file]))  $files[$file] = file($file);
		
		// TODO:: сделать проверку на "UTF-8 w/o BOM" в файле и сообщать через trigger_error, если не так (а также если формат-кодировка файла - MAC )
		
		// извлекаем только нужные строки из файла
		$code = array();
		$line_start = max(1, $line_start);
		$line_end = min($line_end, count($files[$file])+1);
		
		for ($i=$line_start;  $i<=$line_end;  $i++ )
			$code[$i] = trim($files[$file][$i-1]); // учитываем, что в файле строки считаются с 0, а в сообщениях об ощибках  PHP строки начинаются с 1 

		
		// формируем таблицу с пронумерованным кодом		
		$result = '<table class=dbg_code >';
		foreach ($code as $num=>$line)
		{
			$select = $num== $line_end ? 'dbg_select' : ''; 
			$result .=  "<tr>
							<td class='dbg_code_num {$select}' width=20 >$num 
							<td class='{$select}' >" 
							 .html($line)
							//.self::highlight_code($line)
						;
		}
		$result .= '</table>';
		
		return $result;
	
	}
	
	// view_page() - формирует полный HTML-отчет об ошибках 
	static 
	function view_page() 
	{
		if (!self::$is_developer) return self::view_login();
		
		// получаем префикс
		$result = self::view_page_prefix();
		
		// подставляем данные в строки отчета  
		if ($data = self::get_page_data())
		foreach ($data as $row_data) 
		{
			//return get_dump(!stristr($row_data['file'], self::$opt['filter_file']));
			
			// фильтруем по имени файла
			if (self::$opt['filter_file'] 
				&& $row_data['file_url']
				&& !stristr($row_data['file_url'], self::$opt['filter_file'])
				) 
				continue;
			
			//$result .= 	get_dump(!strstr($row_data['file'], self::$opt['filter_file']));
			
			$result .= self::view_page_row($row_data); 
		
		}
		// $result .= get_dump($data);
		//$result .= 123;
		
		// добавляем суффикс
		$result .= self::view_page_suffix();
		
		return $result;
	}
	
	
	// формирует доп. меню дебаггера
	static function view_submenu() 
	{
		$base_url = self::base_url();
		
		return "
		<style>
			.dbg_submenu {
				text-align:right;
				background2:#f0f0f0;
				padding:1px 10px 6px;
				font:13px Verdana; color:#000;
			}
			.dbg_submenu A
			{
				margin: 0 5px 0 10px;
				color:#2545BE;	
				white-space:nowrap;
			}
			
			.dbg_submenu A:hover
			{
				color:red;
				text-decoration:none;
			}
					
		</style>
		
		<div class=dbg_submenu>
			
			Найдено элементов:".count(self::get_page_data())." 
			
			<a href='{$_SERVER['REQUEST_URI']}&clear_log=1'
				>Очистить лог ошибок</a>
			
			<a href='/system/public/php/phpinfo.php' target=_blank 	>phpinfo</a>
			
			<a href='{$base_url}debugger&dbg_showcontent=".(self::$opt['show_content']? 0:1)."' 
				title='Показать или скрыть контент страницы'
				>вкл/выкл контент</a>	
			
			<a href='".($u=self::base_url(true))."' 
				title='Вернуться на страницу $u'
				>на страницу <b>»</b></a>
			
			
		</div>
		
		";
	}
	
	
	
	// формирует краткий отчет дебаггера - с ссылками на страницы дебаггера
	static function view_menu() 
	{	//return '***';
		if (!self::$is_developer) return '';
		
		$base_url 		= self::base_url();
		
		
		$script_time 	= self::script_time();
		$script_memory 	= self::memory_usage_max(); 
		$script_memory_title = self::$memory_usage_max_point;
		
		
		self::$counter['errors_log'] = file_exists(ini_get('error_log'))? round( filesize(ini_get('error_log'))/1024, 1).'Kb' : 0;
		
		return  
		"
		<style>
		#dbg_summary {
			border-top:3px ridge #fff;
			color:#000;
			background:#D4D0C8;
			padding:1px 10px;
			font:12px/20px Verdana;
			height:20px;
			vertical-align:baseline;
		}
		#dbg_summary_bottom {
			border-top:1px solid #fff;
			border-bottom:1px solid #888;
			background:#ccc;
			font-size:0px;
			height:3px;
		}
		#dbg_summary I {
			height:20px;
			width: 4px;
			display:inline-block;
			border-left:1px solid #eee;
			border-right:1px solid #888;
			background:#ccc;
			font-size:0px;
			vertical-align:middle;
			margin:0 5px;
			
		}
		#dbg_summary A {
			color:#000;
			text-decoration:none;
			margin:2px 0;
			padding:3px 3px;
			white-space:nowrap;
		}
		#dbg_summary A:hover {
			
			padding:2px 2px;
			border:1px solid #777;
			border-color: #fff #777 #666 #fff;
			background:#DAD8D2;	
		}
		
		#dbg_summary
		A.dbg_page_".val(self::$opt['page'])." , 
		#dbg_summary
		A.dbg_page_".val(self::$opt['page']).":hover  
		{
			border:1px solid #777;
			border-color: #666 #fff #fff #777;
			background:#DAD8D2;	
			padding:2px 4px;
			font-weight:bold;
			color:#007; 
		}
		
		</style>
		
		<div id=dbg_summary >
		
			<a href= '{$base_url}developer_logout' 
				style='float:right; line-height:13px;' 
				title='Отключить дебаггер и разлогинить разработчика' 
				>выкл</a>
		
			<a href='{$base_url}debugger&dbg_page=warnings'
			class=dbg_page_warnings  
			title='Посмотреть отчет по ошибкам PHP уровня WARNING и ERROR'
			>
			Серьезные ошибки PHP: <b>".self::$counter['errors_warnings']."</b>
			</a>
			<i></i>
			
			<a href='{$base_url}debugger&dbg_page=errors_all'   
			class=dbg_page_errors_all 
			title='Посмотреть отчет по ошибкам PHP всех уровней'
			>
			Все ошибки PHP: <b>".self::$counter['errors_all']."</b>
			</a>
			<i></i>
			
			<a href='{$base_url}debugger&dbg_page=errors_log'   
			class=dbg_page_errors_log 
			title='Изучить лог ошибок'
			>
			Лог ошибок: <b>".self::$counter['errors_log']."</b>
			</a>
			<i></i>
			
			
			<a href='{$base_url}debugger&dbg_page=time' 
			class=dbg_page_time 
			title='Посмотреть отчет по расходу времени в скрипте'
			>
			Расход времени: <b>" . $script_time . " ms</b>
			</a>
			<i></i>
		"
		.($script_memory 
			? ("<a href='{$base_url}debugger&dbg_page=memory' 
				class=dbg_page_memory  
				title='Посмотреть отчет по расходу памяти в скрипте'
				>
				Расход памяти: <b>" . $script_memory . " Kb</a>
				<i></i>" )
			:"") 
		
		.(phpversion('xdebug') 
			? " <a href='".self::current_url()."XDEBUG_PROFILE' 
					title='Отчет xDebug по расходу времени (профайл)'
					
				>Отчет xDebug</a>
				<i></i>
				"
			:"" )

		."
			
		</div>
		<div id=dbg_summary_bottom></div>
		
		";
		
	}
	
	static
	function view_page_prefix()
	{
		$base_url = self::base_url();
		
		return  self::view_menu() . 
		"
		<style>
		
			body, html {margin:0; padding:0;}
			h4 {
				margin:10px 0; font-family:Arial;
			}
			.dbg_PARSE_ERROR,	.dbg_CORE_ERROR,
			.dbg_COMPILE_ERROR, .dbg_ERROR,
			.dbg_USER_ERROR ,	.dbg_RECOVERABLE_ERROR, 
			.dbg_ { 
				color:#f00; 
				}
			
			.dbg_WARNING, .dbg_CORE_WARNING,
			.dbg_COMPILE_WARNING , .dbg_USER_WARNING , 
			.dbg_ { 
				color: #FD7403
				}
			
			.dbg_NOTICE , .dbg_USER_NOTICE, .dbg_STRICT_STANDARTS_NOTICE , 
			.dbg_ {
				color: #777
				}
			
			.dbg_UNKNOWN_ERROR {
				color:black
				}
			
		
			
			.dbg_dashed {
				border-bottom: 1px dashed #000;
				}
			A.dbg_dashed {
				border-bottom: 1px dashed #2545BE;	
				text-decoration:none;
				}
			A.dbg_dashed:hover {
				border-bottom: 0;	
				text-decoration:none;
				}
			
			.dbg_dump	{
				background: #ffc;
				padding:2px 5px 20px 10px; margin: 4px 0;
				border:1px solid #ddc;
				display:block;
				
			}
			.dbg_page {
					font:13px Verdana; color:#000;
					background:#fff;
					padding: 3px 0px;
					margin: 0 6px;
					border-bottom2: 1px dashed #999;
					
					}
					
			.dbg_page A {
				color:#2545BE;	
				white-space:nowrap;
			}
			
			.dbg_page A:hover {
				color:red;
				text-decoration:none;
			}
			
			.dbg_page SUP { 
				color:#999;
				}
			
			.dbg_row {
				width:600px;
				border-bottom: 1px dashed #999;
				margin-top:5px;
				padding:8px 0px 5px;
				}
			
			.dbg_file { 
				color:#090; 
				}
			.dbg_file A {
				color:#090;
				text-decoration2:none;
				border-bottom2:1px solid #999;
			}
			
			.dbg_row .code {
				background: #eee;
				margin:5px 0;
			}
			
			TABLE.dbg_info {
				font-size: 13px;
				width:600px;
			}
			
			TABLE.dbg_code {
				width:600px;
				background: #f5f5f5;
				border:1px solid #bbb;
				margin:6px 0 ;
				border-collapse: collapse; 
				color:#777;
				}
			.dbg_code TD {
				font:14px monospace;
				padding:3px 5px;
			}	
			.dbg_code TD.dbg_code_num 				{
				background: #d4d4d5;
				color:#555;
				padding:3px 6px 3px 3px;
				vertical-align:top;
				}
			.dbg_code TD.dbg_select {
				color:black; 
				font-weight:bold;
				font-size:15px;
				}
			
		</style>
		
		".self::view_submenu()."
		
		<div class=dbg_page>
		";
	}	
	
	
	// возвращает HTML-код для строки отчета
	static
	function view_page_row($data)
	{	
		$page = self::$opt['page'];
		
		
		
		if ( $page=='warnings' OR $page=='errors_all' OR $page=='errors_log' )
			
			return self::view_page_row_errors_list($data); 
			
		if ( $page=='error_dump')
			return self::view_page_row_error_dump($data); 
		
	}
	
	static
	function view_page_row_errors_list($error)
	{
		// выводим срок с информацией об ошибке
		$result = "<div class=dbg_row>";
		
		// return (get_dump($error) );
		
		
		// путь к файлу
		$result .= "<span class=dbg_file >
			<a title='Отфильтровать по имени файла'
			href='"
			.self::base_url()
			."debugger"
			."&dbg_page=".GET('dbg_page')	
			."&dbg_filter_file=" .$error['file_url'] 
			."' >{$error['file_url']}</a> : {$error['line']}</span>"; 
			
		// PHP код , где была ошибка 
		$result .= self::get_code( $error['file'], 
						$error['line'] - 4, $error['line'] );
		

		if(!empty($error['date'])) $result .= "<b style='color:#c90'>[$error[date]]</b>";
		$title= html(val($error['error_str']));
		
		$result .= "
			<b title='$title' style='color:red'>{$error['message']}</b><br/>";
		
		$result .= 
		"
			<div></div>
			<span class='dbg_{$error['type']}' >{$error['type']}</span>
			
			
		";
		// in: <span title='{$error[file_url]}' ></span>&nbsp; line: 
		// ссылка на исследование ошибки
		
		if (empty($error['date']))
		$result .= "
			<a href='".self::base_url()."debugger&dbg_page=error_dump&error_key=$error[key]&error_point=$error[file_url]:$error[line]' >исследовать ошибку</a>
		";	
		
		$result .= "</div>";
		
		return $result;
	}
	
	
	static
	function view_page_row_error_dump($error)
	{	
		// Сообщение об ошибке 
		$result = "
		<h4>Error back trace:</h4>
		<div class=dbg_row>
		" //.get_dump($error['backtrace'])
		;
		
		if(empty($error['backtrace']))
			$error['backtrace'] = array($error);
		
		// отображаем стек вызов функций (backtrace)
		// + PHP код + дампы переменных
		foreach ($error['backtrace'] as $i=>$call )
		{
			if (empty($call['file'])) continue;
			
			
			
			// обрабатываем сообщение об ошибке eval() code 
			if (strstr($call['file'], '('))
			{
				$call['line_eval'] = $call['line'];
				list ($call['file'], $call['line'])=
					explode('(', $call['file'] );
				
				$call['line'] = intval($call['line']);
				$call['message'] .= " (eval() code error in line $error[line_eval])"; 
			}
			
			
			// добавляем табличку с пронумерованным PHP кодом 
			$result .= self::get_code( $call['file'], 
								$call['line'] - 6, 
								$call['line'] 
								);
			
			if (empty($first_done))
			{
			
			$result .= "
				
				<div>
				<span class='dbg_{$error["type"]}' >{$error["type"]}:</span> 
				<b style='color:red'>{$error["message"]}</b>
				</div>
				
			";
			}
			

			
			// вычисляем составное имя функции или метода
			$function = $call['function'];
			if (val($call['type'])=='::')  
				$function = $call['class'].'::'.$function;
			if ($call['type']=='->')
				$function = "($call[class])Object->".$function;
			if($call['function']=='error_handler') $function='';
			
			// вычисляем id для дампов переменных
			$span_id_args = 'dbg_args_'.$i;
			$span_id_object = 'dbg_object_'.$i;
			$span_id_context = 'dbg_context_'.$i;
			
			// выводим ссыки для отображения дампов
			$result .= "<div>";
			
			// ссылка на дамп аргументов функции
			if ($function)
			{
				$result .= "Function: <b>".$function."</b>(";
				
				if(count($call['args']))
					$result .= "
					<a href='javascript:dump_toggle(\"{$span_id_args}\");' onclick='this.blur();' class=dbg_dashed >function args</a><SUP>".count($call['args'])."</SUP>";
					
				$result .= ")";	
			}
			
			// ссылка на дамп переменных объекта  in_array
			if (!empty($call['object']))
				$result .= 
				"&nbsp;
					( <a href='javascript:dump_toggle(\"{$span_id_object}\");' onclick='this.blur();' class=dbg_dashed >object vars</a><SUP>".count((array)$call['object'])."</SUP> )
				";
			
			$result .= "</div>";
			
			// дамп аргументов функции
			$result .= 
				"<span id='{$span_id_args}' class=dbg_dump style='display:none'	 >"
				."<a href='javascript:dump_toggle(\"{$span_id_args}\");'
				class=dbg_dashed style='float:right'
				>close</a>"
				."<b>function args</b>"
				.get_dump($call['args'])
				
				."<a href='javascript:dump_toggle(\"{$span_id_args}\");'
				class=dbg_dashed style='float:right'
				>close</a>"
				."</span>";	
			
			// дамп переменных объекта
			if (!empty($call['object']))
			$result .=
				"<span id='{$span_id_object}' class=dbg_dump style='display:none'	 >"
				."<a href='javascript:dump_toggle(\"{$span_id_object}\");'
				class=dbg_dashed style='float:right'
				>close</a>"
				."<b>object vars</b>"
				.get_dump($call['object'])
				."<a href='javascript:dump_toggle(\"{$span_id_object}\");'
				class=dbg_dashed style='float:right'
				>close</a>"
				."</span>";		
			
			
			// путь к файлу и номер строки
			$result .= 
			" 
				<div2>
				File: <span title='{$call["file"]}' >"
				.path2url($call["file"])
				."</span>&nbsp; line: {$call["line"]}
				<div2/>
			";	
			
			// ссылка на дамп переменных контектса (в месте ошибки)
			if (empty($first_done) && !empty($error['context'])) 
				$result .= 
				"&nbsp;( <a href='javascript:dump_toggle(\"{$span_id_context}\");' onclick='this.blur();' class=dbg_dashed >context vars</a><SUP>".count($error['context'])."</SUP> )
				";
			
			// дамп переменных контекста
			if (empty($first_done))
			$result .=
				"<span id='{$span_id_context}' class=dbg_dump style='display:none'	 >"
				."<a href='javascript:dump_toggle(\"{$span_id_context}\");'
				class=dbg_dashed style='float:right'
				>close</a>"
				."<b>context vars</b>"
				.get_dump($error['context'])
				."<a href='javascript:dump_toggle(\"{$span_id_context}\");'
				class=dbg_dashed style='float:right'
				>close</a>"
				."</span>";	
			
			
			// стрелка вверх	
			if ($i != count($error['backtrace'])-1)
				$result .= "
				<div style='width:150px; text-align:center;'>
				<b style='font-size:20px; line-height:10px;'>▲</b><br/>|<br/>|<br/>|
				</div>
				"; 
			
			
		
			$first_done=true;
		}
			
		$result .= '</div>';
		
		
		return $result;
	}
	
	static
	function view_page_row_time($data)
	{
	
	}

	static
	function view_page_row_memory($data)
	{
	
	}

	static
	function view_page_suffix()
	{
		return "</div>";
	}
	
	
	static
	function view_login()
	{
		return "
			<form method=POST>
			<input type=password name=developer_pass style='border2:0'>
			</form>
		";
	}
	
	
	
}	








/*  Это нужно испытать и использовать с осторожностью

// This is a function that should work for both Windows XP/2003 and most distrabutions of UNIX and Mac OS X.


if( !function_exists('memory_get_usage') ) 
{ 
    function memory_get_usage() 
    { 
        //If its Windows 
        //Tested on Win XP Pro SP2. Should work on Win 2003 Server too 
        //Doesn't work for 2000 
        //If you need it to work for 2000 look at http://us2.php.net/manual/en/function.memory-get-usage.php#54642 
        if ( substr(PHP_OS,0,3) == 'WIN') 
        { 
               if ( substr( PHP_OS, 0, 3 ) == 'WIN' ) 
                { 
                    $output = array(); 
                    exec( 'tasklist /FI "PID eq ' . getmypid() . '" /FO LIST', $output ); 
        
                    return preg_replace( '/[\D]/', '', $output[5] ) * 1024; 
                } 
        }else 
        { 
            //We now assume the OS is UNIX 
            //Tested on Mac OS X 10.4.6 and Linux Red Hat Enterprise 4 
            //This should work on most UNIX systems 
            $pid = getmypid(); 
            exec("ps -eo%mem,rss,pid | grep $pid", $output); 
            $output = explode("  ", $output[0]); 
            //rss is given in 1024 byte units 
            return $output[1] * 1024; 
        } 
    } 
} 


// * A memory_get_usage() for Windows System, wich compiled without --enable-memory-limit 
// * PHP must be loaded as CGI
// * Greetings form miteigi nemoto
// * @return string

function memory_get_usage_by_tasklist()
{
    $output = array();
    exec( 'tasklist ', $output );
    foreach ($output as $value)
    {
        $ex=explode(" ",$value);
        $count_ex=count($ex);
        if (eregi(" ".getmypid()." Console",$value))
        {
            $memory_size=$ex[$count_ex-2]." Kb";
            return $memory_size;
        }
    }
}



// Original from http://php.net/memory_get_usage by websjwans at hotmail dot com. Thanks alot!
if ( ! function_exists( 'memory_get_usage' ) ):
    // Only define function if it doesn't exist
    function memory_get_usage()
    {
        // If we are running on Windows
        if ( substr( PHP_OS, 0, 3 ) == 'WIN' ):
            $output = array();
            // Should check whether tasklist is available, but I'm lazy
            exec( 'tasklist /FI "PID eq ' . getmypid() . '" /FO LIST', $output );
            // Filter non-numeric characters from output. Why not use substr & strpos?
            // I'm running Windows XP Pro Dutch, and it's output does not match the
            // English variant, as will all other translations. This is a more generic
            // approach, and has a better chance of actually working
            return preg_replace( '/[^0-9]/', '', $output[5] ) * 1024;
            // Tasklist outputs memory usage in kilobytes, memory_get_usage in bytes.
            // So we multiply by 1024 and in the process convert from string to integer.
        else:
            return false;
        endif;
    }
endif;






 */


